package cgl.narada.topicgenerator;

/**
 * <p>Title: LongTopicGenerator.java
 * This class is used to generate long topic numbers. </p>
 * <p>Description:
 * it receives LongTopicRequest messages and sends LongTopicResponse messages back.
 * </p>
 * <p>Copyright: Copyright (c) 2004</p>
 * <p>Company: </p>
 * @author not attributable
 * @version 1.0
 */

import java.util.ArrayList;

import cgl.narada.protocol.ClientConnectionHandler;
import cgl.narada.protocol.NodeAddress;

public class LongTopicGenerator extends Thread{

   // this is used to send the response message
   private ClientConnectionHandler clientConnectionHandler;

   // the list of request messages
   private ArrayList requestList;

   // this is used to sleep the thread of this class
   private Object synchObject;

   // this is used to close the thread
   private boolean closed = false;

   //this shows the last used time stamp value
   private long lastTimestamp;

   // broker node address, first 20 bits are set.
   // this is common in all topics generated by this broker
   private long nodeAddress;

   // max time stamp is 44 bits, if all the bits are set, then it is
   private long MAX_TIME_STAMP;

   public LongTopicGenerator(NodeAddress brokerNodeAddress, ClientConnectionHandler cch) {
      this.clientConnectionHandler = cch;

      requestList = new ArrayList();
      synchObject = new Object();

      // set max time stamp variable
      long one = 1;
      MAX_TIME_STAMP = one << 44; // set 45th bit
      MAX_TIME_STAMP = MAX_TIME_STAMP -1; // subtract one to obtain all 44 bits set long

//      if( isNodeAddressValid(brokerNodeAddress) ){
//         setNodeAddress(brokerNodeAddress);
//      }

      // initialize last topic as the start time
      lastTimestamp = System.currentTimeMillis();

   }

   public void setNodeAddress(NodeAddress brokerNodeAddress){

      try{
         convertNodeAddress(brokerNodeAddress.getAddressInInts());
      }catch(Exception e){
         System.out.println("!!!!! LongTopicGenerator is not initialized properly !!!!!");
         return;
//         e.printStackTrace();
      }

      //start the thread
      start();

      System.out.println("LongTopicGenerator is initialized.");
   }

   private boolean isNodeAddressValid(NodeAddress brokerNodeAddress){
      try{
         long index1 = getSetIndex(brokerNodeAddress.getAddressInInts()[0]);
         long index2 = getSetIndex(brokerNodeAddress.getAddressInInts()[1]);
         long index3 = getSetIndex(brokerNodeAddress.getAddressInInts()[2]);
         long index4 = getSetIndex(brokerNodeAddress.getAddressInInts()[3]);
         return true;
      }catch(Exception e){
         return false;
      }
   }

   /**
    * @return return the bits of a long as a String
    */
   private String getLongString(long l){

      byte[] byteArray = cgl.narada.util.ByteUtilities.getBytes(l);
      String total = "";
      for (int i = 0; i < byteArray.length; i++) {
         total = total + getByteAsString(byteArray[i]) +" ";
      }

      return total;
   }

   /**
    * convert the node address bytes to 20bits, and set the first 20 bits of a long.
    */
   private void convertNodeAddress(int[] brokerNodeAddress) throws Exception{

      long index1 = getSetIndex(brokerNodeAddress[0]);
      long index2 = getSetIndex(brokerNodeAddress[1]);
      long index3 = getSetIndex(brokerNodeAddress[2]);
      long index4 = getSetIndex(brokerNodeAddress[3]);

      // since indexes go from 1-32, we subtract 1 to fit in 5 bits
      // if we don't subtract 1, then 00000 is not used and 32 goes to 6th bit.
      index1 = index1 -1;
      index2 = index2 -1;
      index3 = index3 -1;
      index4 = index4 -1;

      // set all bits to zero
      nodeAddress = 0;

      // add the first 5 bits to the most significant 5 bits
      index1 = index1 << 59;
      nodeAddress = nodeAddress | index1;

      // add the second 5 bits
      index2 = index2 << 54;
      nodeAddress = nodeAddress | index2;

      // add the third 5 bits
      index3 = index3 << 49;
      nodeAddress = nodeAddress | index3;

      // add the second 5 bits
      index4 = index4 << 44;
      nodeAddress = nodeAddress | index4;
   }

   /**
    * returns the index of the first bit set, starting from idex.
    * It examines the 4 bytes from the index.
    * if there are more than one bit set, throws an exception
    * if there is no bit set, returns -1
    */
   private int getSetIndex(int x) throws Exception{

      if(x == 0){
         throw new Exception("There is no set bit in the given int: "+x);
      }

      int y = 1;
      for (int i = 0; i < 32; i++) {
         if( (y & x) == y){
            return i+1;
         }
         y = y << 1;
      }

      throw new Exception("There are multiple bits set in the given int: "+x);
   }

   /**
    * process a request
    * response message is published through ClientConnectionHandler object
    * @param receivedData received Request object in serialized form.
    */
   public void generate(byte[] receivedData){

      LongTopicRequest request = new LongTopicRequest(receivedData);
      requestList.add(request);
      synchronized(synchObject){
         synchObject.notify();
      }

   }

   public void run(){

      while(!closed){
         while(requestList.size() != 0){
            LongTopicRequest request = (LongTopicRequest)requestList.remove(0);
            processRequest(request);
         }
         try{
            synchronized(synchObject){
               synchObject.wait();
            }
         }catch(Exception e){
            e.printStackTrace();
//            logger.log(Level.WARNING, "problem when waiting", e);
         }
      }
   }

   /**
    * this method process a single request
    * it sends the response message through clientConnectionHandler
    * @param request the request message to be processed
    */
   private void processRequest(LongTopicRequest request){

      long currentTime = System.currentTimeMillis();
      int numberOfTopics = request.getNumberOfTopics();
      long baseTimestamp;

      if( (lastTimestamp + numberOfTopics) < currentTime){
         baseTimestamp = lastTimestamp;
         lastTimestamp = lastTimestamp + numberOfTopics;
      }else{
         long duration = (lastTimestamp + numberOfTopics) - currentTime;
         try{
            Thread.sleep(duration);
         }catch(Exception e){
            e.printStackTrace();
         }
         baseTimestamp = lastTimestamp;
         lastTimestamp = lastTimestamp + numberOfTopics;
      }

      LongTopicResponse response;

      // check whether baseTimestamp is mode than 44 bits
      if(baseTimestamp > this.MAX_TIME_STAMP){
         response = new LongTopicResponse(request.getClientID());
         System.err.println(this.getClass().getName()+ " Request to create Long Topic Number is failed.");
         System.err.println("Base timestamp value[ "+baseTimestamp+" ] is bigger than MAX_TIME_STAMP value[ "+MAX_TIME_STAMP+" ]");
      }else{
         // add the node address to the baseTimestamp
         baseTimestamp = baseTimestamp | nodeAddress;
         response = new LongTopicResponse(request.getClientID(), baseTimestamp, request.getNumberOfTopics());
      }

      this.clientConnectionHandler.sendToClientNode(response.getBytes(), request.getClientID());
      System.out.println(this.getClass().getName()+" LongTopicResponse is sent:\n"+ response);

//      System.out.println("Response is sent. TopicNumber: "+baseTopic);
   }


   /**
    * stops the thread
    */
   public void close() {

      closed = true;
      synchronized (synchObject) {
         synchObject.notify();
      }
   }

   /**
    * convert a byte to a string representing the bit values.
    */
   private StringBuffer getByteAsString(byte b){

      int x = 128; // this is 10000000.

      StringBuffer buffer = new StringBuffer();

      for(int i=0; i<8; i++){

         if( ( b & x) == x){
            buffer.append(1);
         }else{
            buffer.append(0);
         }

         x = x / 2;
      }

      return buffer;
   }



}